package com.javaspeak.designpatterns.go4.behavioural.state;

/**
 * This example uses the state pattern.  State objects that extends an abstract
 * state class can be switched from one to the other at runtime.
 * <p>
 * A StateContext delegates methods calls to the current state object.  The
 * StateContext object has the same method names as the State objects.
 * <p>
 * During a method call the State object can decide to switch it's self for
 * another State object.
 * <p>
 * In other words some or all of the methods in the State object have the
 * ability to switch the State object for another state object.
 * <p>
 * The new state object also extends the same abstract state object and has the
 * same method signatures, however the implementation of the methods are likely
 * to be different.  This pattern uses polymorphism to modifiy the behaviour at
 * runtime.
 * <p>
 * In this example the ApplicationState class is the StateContext and holds a
 * reference to the current State Object extending AbstractAccountState. There
 * are 3 classes that extend AbstractAccountState: StarterAccountState,
 * StandardAccountState and PremiereAccountState.
 * <p>
 * This example models a Bank Account that can be upgraded or downgraded
 * according to the current annual salary of the account holder.
 * The setSalary( int salary ) method in ApplicationState delegates to
 * the current State Object by calling its
 * setSalary( StateContext stateContext, int salary ) method. Notice that
 * ApplicationState passes a reference of itself ( StateContext stateContext )
 * in the setSalary(..) method.  The reason it does this is so that the State
 * Object, e.g. StandardAccountState can then call the stateContext to switch
 * the current State object for another one:
 * <pre>
 * {@code
 * if ( salary >= SalaryGrade.PREMIERE_ACCOUNT.getSalary() ){
 *
 *     stateContext.changeState(
 *            new PremiereAccountState( salary, balance ) );
 * }
 * else if ( ! ( salary >= SalaryGrade.STANDARD_ACCOUNT.getSalary() ) ){
 *
 *     stateContext.changeState(
 *            new StarterAccountState( salary, balance ) );
 * }
 * else {
 *
 *     this.salary = salary;
 * }
 * </pre>
 * Infact changing the salary via the setSalary(..) method can consequently
 * result in the account being upgraded or downgraded.  Notice that not all
 * method calls of ApplicationState delegated to a state object result in
 * the State object being swapped for another State Object. For example the
 * setBalance(..) method does not result in the state object switching, the
 * reason for this is that the current bank account type depends on the annual
 * salary and not the current bank balance.
 *
 * @author John Dickerson
 */
public class ApplicationState implements StateContext {

    private AbstractAccountState accountState;

    /**
     * Initialize state with the Starter Bank Account and a balance of 200 GBP
     */
    public ApplicationState(){

        accountState = new StarterAccountState( 0, 200 );
    }

    /* (non-Javadoc)
     * @see com.javaspeak.designpatterns.go4.behavioural.state.
     *      StateContext#changeState(com.javaspeak.designpatterns.go4.
     *          behavioural.state.State)
     */
    @Override
    public void changeState( AbstractAccountState newState ) {

        accountState = newState;
    }


    /* (non-Javadoc)
     * @see com.javaspeak.designpatterns.go4.behavioural.state.
     *      StateContext#setSalary(int)
     */
    @Override
    public void setSalary( int salary ) {

        accountState.setSalary( this, salary );
    }


    /* (non-Javadoc)
     * @see com.javaspeak.designpatterns.go4.behavioural.state.
     *      StateContext#getSalary()
     */
    @Override
    public int getSalary() {

        return accountState.getSalary();
    }


    /* (non-Javadoc)
     * @see com.javaspeak.designpatterns.go4.behavioural.state.
     *      StateContext#getBalance()
     */
    @Override
    public float getBalance() {

        return accountState.getBalance();
    }


    /* (non-Javadoc)
     * @see com.javaspeak.designpatterns.go4.behavioural.state.
     *      StateContext#setBalance(float)
     */
    @Override
    public void setBalance( float balance ) {

        accountState.balance = balance;
    }


    /* (non-Javadoc)
     * @see com.javaspeak.designpatterns.go4.behavioural.state.
     *      StateContext#getOverdraft()
     */
    @Override
    public int getOverdraft() {

        return accountState.getOverdraft();
    }


    /* (non-Javadoc)
     * @see com.javaspeak.designpatterns.go4.behavioural.state.
     *      StateContext#getAccountName()
     */
    @Override
    public String getAccountName() {

        return accountState.getAccountName();
    }


    /**
     * Run the example.  Note the accountState.toString() method is in the
     * AbstractAccountState which the state objects extend.  The toString()
     * method prints out the state of the object.
     * <p>
     * Notice that the setSalary(..) method in this class delegates to the
     * current state object and calls the state object's setSalary(..) method.
     * Notice that the setSalary(..) method of the state object has the ability
     * to switch the current bank account state object to another one.
     */
    private void runExample() {

        System.out.println( "===============================================" );
        setBalance( 500 );
        System.out.println( accountState.toString() );
        System.out.println( "===============================================" );
        setSalary( 4000 );
        setBalance( 1000 );
        System.out.println( accountState.toString() );
        System.out.println( "===============================================" );
        setSalary( 7200 );
        setBalance( 3000 );
        System.out.println( accountState.toString() );
        System.out.println( "===============================================" );
        setSalary( 82000 );
        setBalance( 7000 );
        System.out.println( accountState.toString() );
        System.out.println( "===============================================" );
    }


    /**
     * @param args
     */
    public static void main(String[] args) {

        ApplicationState application = new ApplicationState();
        application.runExample();
    }
}
